package org.eclipse.nebula.widgets.xviewer.util.internal;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.nebula.widgets.xviewer.util.XViewerDisplay;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.UIStats;
import org.eclipse.ui.internal.progress.ProgressMessages;

/**
 * The UIJob is a Job that runs within the UI Thread via an asyncExec.
 * 
 * @since 3.0
 */
@SuppressWarnings("restriction")
public abstract class XViewerUIJob extends Job {
   private Display cachedDisplay;

   /**
    * Create a new instance of the receiver with the supplied name. The display used will be the one from the workbench
    * if this is available. UIJobs with this constructor will determine their display at runtime.
    * 
    * @param name the job name
    */
   public XViewerUIJob(String name) {
      super(name);
   }

   /**
    * Create a new instance of the receiver with the supplied Display.
    * 
    * @param jobDisplay the display
    * @param name the job name
    * @see Job
    */
   public XViewerUIJob(Display jobDisplay, String name) {
      this(name);
      setDisplay(jobDisplay);
   }

   /**
    * Convenience method to return a status for an exception.
    * 
    * @param exception
    * @return IStatus an error status built from the exception
    * @see Job
    */
   public static IStatus errorStatus(Throwable exception) {
      return WorkbenchPlugin.getStatus(exception);
   }

   /**
    * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) Note: this message is marked
    * final. Implementors should use runInUIThread() instead.
    */
   @Override
   public final IStatus run(final IProgressMonitor monitor) {
      if (monitor.isCanceled()) {
         return Status.CANCEL_STATUS;
      }
      Display asyncDisplay = getDisplay();
      if (asyncDisplay == null || asyncDisplay.isDisposed()) {
         return Status.CANCEL_STATUS;
      }
      asyncDisplay.asyncExec(new Runnable() {
         @Override
         public void run() {
            IStatus result = null;
            Throwable throwable = null;
            try {
               //As we are in the UI Thread we can
               //always know what to tell the job.
               setThread(Thread.currentThread());
               if (monitor.isCanceled()) {
                  result = Status.CANCEL_STATUS;
               } else {
                  UIStats.start(UIStats.UI_JOB, getName());
                  result = runInUIThread(monitor);
               }

            } catch (Throwable t) {
               throwable = t;
            } finally {
               UIStats.end(UIStats.UI_JOB, XViewerUIJob.this, getName());
               if (result == null) {
                  result =
                     new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, ProgressMessages.InternalError,
                        throwable);
               }
               done(result);
            }
         }
      });
      return Job.ASYNC_FINISH;
   }

   /**
    * Run the job in the UI Thread.
    * 
    * @param monitor
    * @return IStatus
    */
   public abstract IStatus runInUIThread(IProgressMonitor monitor);

   /**
    * Sets the display to execute the asyncExec in. Generally this is not' used if there is a valid display available
    * via PlatformUI.isWorkbenchRunning().
    * 
    * @param runDisplay Display
    * @see XViewerUIJob#getDisplay()
    * @see PlatformUI#isWorkbenchRunning()
    */
   public void setDisplay(Display runDisplay) {
      Assert.isNotNull(runDisplay);
      cachedDisplay = runDisplay;
   }

   /**
    * Returns the display for use by the receiver when running in an asyncExec. If it is not set then the display set in
    * the workbench is used. If the display is null the job will not be run.
    * 
    * @return Display or <code>null</code>.
    */
   public Display getDisplay() {
      //If it was not set get it from the workbench
      if (cachedDisplay == null) {
         cachedDisplay = XViewerDisplay.getDisplay();
      }
      return cachedDisplay;
   }
}
